// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;
using System.CommandLine.Parsing;
using Microsoft.DotNet.Cli.Commands.New.MSBuildEvaluation;
using Microsoft.DotNet.Cli.Commands.New.PostActions;
using Microsoft.DotNet.Cli.Commands.Workload;
using Microsoft.DotNet.Cli.Commands.Workload.List;
using Microsoft.DotNet.Cli.Extensions;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Abstractions.Components;
using Microsoft.TemplateEngine.Abstractions.Constraints;
using Microsoft.TemplateEngine.Abstractions.TemplatePackage;
using Microsoft.TemplateEngine.Cli;
using Microsoft.TemplateEngine.Cli.Commands;
using Microsoft.TemplateEngine.Cli.PostActionProcessors;
using Command = System.CommandLine.Command;

namespace Microsoft.DotNet.Cli.Commands.New;

internal static class NewCommandParser
{
    public static readonly string DocsLink = "https://aka.ms/dotnet-new";
    public const string CommandName = "new";
    private const string EnableProjectContextEvaluationEnvVarName = "DOTNET_CLI_DISABLE_PROJECT_EVAL";
    private const string PrefferedLangEnvVarName = "DOTNET_NEW_PREFERRED_LANG";

    private const string HostIdentifier = "dotnetcli";

    private const VerbosityOptions DefaultVerbosity = VerbosityOptions.normal;

    private static readonly Option<bool> s_disableSdkTemplatesOption = new Option<bool>("--debug:disable-sdk-templates")
    {
        DefaultValueFactory = static _ => false,
        Description = CliCommandStrings.DisableSdkTemplates_OptionDescription,
        Recursive = true
    }.Hide();

    private static readonly Option<bool> s_disableProjectContextEvaluationOption = new Option<bool>(
        "--debug:disable-project-context")
    {
        DefaultValueFactory = static _ => false,
        Description = CliCommandStrings.DisableProjectContextEval_OptionDescription,
        Recursive = true
    }.Hide();

    private static readonly Option<VerbosityOptions> s_verbosityOption = new("--verbosity", "-v")
    {
        DefaultValueFactory = _ => DefaultVerbosity,
        Description = CliCommandStrings.Verbosity_OptionDescription,
        HelpName = CliStrings.LevelArgumentName,
        Recursive = true
    };

    private static readonly Option<bool> s_diagnosticOption =
        CommonOptionsFactory
            .CreateDiagnosticsOption(recursive: true)
            .WithDescription(CliCommandStrings.Diagnostics_OptionDescription);

    internal static readonly Command s_command = GetCommand();

    public static Command GetCommand()
    {
        Command command = NewCommandFactory.Create(CommandName, (Func<ParseResult, CliTemplateEngineHost>)GetEngineHost);
        command.Options.Add(s_disableSdkTemplatesOption);
        command.Options.Add(s_disableProjectContextEvaluationOption);
        command.Options.Add(s_verbosityOption);
        command.Options.Add(s_diagnosticOption);
        return command;

        static CliTemplateEngineHost GetEngineHost(ParseResult parseResult)
        {
            bool disableSdkTemplates = parseResult.GetValue(s_disableSdkTemplatesOption);
            bool disableProjectContext = parseResult.GetValue(s_disableProjectContextEvaluationOption)
                || Env.GetEnvironmentVariableAsBool(EnableProjectContextEvaluationEnvVarName);
            bool diagnosticMode = parseResult.GetValue(s_diagnosticOption);
            FileInfo? projectPath = parseResult.GetValue(SharedOptions.ProjectPathOption);
            FileInfo? outputPath = parseResult.GetValue(SharedOptions.OutputOption);

            OptionResult? verbosityOptionResult = parseResult.GetResult(s_verbosityOption);
            VerbosityOptions verbosity = DefaultVerbosity;

            if (diagnosticMode || CommandLoggingContext.IsVerbose)
            {
                CommandLoggingContext.SetError(true);
                CommandLoggingContext.SetOutput(true);
                CommandLoggingContext.SetVerbose(true);
                verbosity = VerbosityOptions.diagnostic;
            }
            else if (verbosityOptionResult != null
                && !verbosityOptionResult.Implicit
                // if verbosityOptionResult contains an error, ArgumentConverter.GetValueOrDefault throws an exception
                // and callstack is pushed to process output
                && !parseResult.Errors.Any(error => error.SymbolResult == verbosityOptionResult))
            {
                VerbosityOptions userSetVerbosity = verbosityOptionResult.GetValueOrDefault<VerbosityOptions>();
                if (userSetVerbosity.IsQuiet())
                {
                    CommandLoggingContext.SetError(false);
                    CommandLoggingContext.SetOutput(false);
                    CommandLoggingContext.SetVerbose(false);
                }
                else if (userSetVerbosity.IsMinimal())
                {
                    CommandLoggingContext.SetError(true);
                    CommandLoggingContext.SetOutput(false);
                    CommandLoggingContext.SetVerbose(false);
                }
                else if (userSetVerbosity.IsNormal())
                {
                    CommandLoggingContext.SetError(true);
                    CommandLoggingContext.SetOutput(true);
                    CommandLoggingContext.SetVerbose(false);
                }
                verbosity = userSetVerbosity;
            }
            Reporter.Reset();
            return CreateHost(disableSdkTemplates, disableProjectContext, projectPath, outputPath, parseResult, verbosity.ToLogLevel());
        }
    }

    private static CliTemplateEngineHost CreateHost(
        bool disableSdkTemplates,
        bool disableProjectContext,
        FileInfo? projectPath,
        FileInfo? outputPath,
        ParseResult parseResult,
        LogLevel logLevel)
    {
        var builtIns = new List<(Type InterfaceType, IIdentifiedComponent Instance)>();
        builtIns.AddRange(TemplateEngine.Orchestrator.RunnableProjects.Components.AllComponents);
        builtIns.AddRange(TemplateEngine.Edge.Components.AllComponents);
        builtIns.AddRange(Components.AllComponents);
        builtIns.AddRange(TemplateSearch.Common.Components.AllComponents);

        //post actions
        builtIns.AddRange(
        [
            (typeof(IPostActionProcessor), new DotnetAddPostActionProcessor()),
            (typeof(IPostActionProcessor), new DotnetSlnPostActionProcessor()),
            (typeof(IPostActionProcessor), new DotnetRestorePostActionProcessor())
        ]);
        if (!disableSdkTemplates)
        {
            builtIns.Add((typeof(ITemplatePackageProviderFactory), new BuiltInTemplatePackageProviderFactory()));
            builtIns.Add((typeof(ITemplatePackageProviderFactory), new OptionalWorkloadProviderFactory()));
        }
        if (!disableProjectContext)
        {
            builtIns.Add((typeof(IBindSymbolSource), new ProjectContextSymbolSource()));
            builtIns.Add((typeof(ITemplateConstraintFactory), new ProjectCapabilityConstraintFactory()));
            builtIns.Add((typeof(MSBuildEvaluator), new MSBuildEvaluator(outputDirectory: outputPath?.FullName, projectPath: projectPath?.FullName)));
        }

        builtIns.Add((typeof(IWorkloadsInfoProvider), new WorkloadsInfoProvider(
                new Lazy<IWorkloadsRepositoryEnumerator>(() => new WorkloadInfoHelper(parseResult.HasOption(SharedOptions.InteractiveOption)))))
        );
        builtIns.Add((typeof(ISdkInfoProvider), new SdkInfoProvider()));

        string? preferredLangEnvVar = Environment.GetEnvironmentVariable(PrefferedLangEnvVarName);
        string preferredLang = string.IsNullOrWhiteSpace(preferredLangEnvVar) ? "C#" : preferredLangEnvVar;

        var preferences = new Dictionary<string, string>
        {
            { "prefs:language", preferredLang },
            { "dotnet-cli-version", Product.Version },
            { "RuntimeFrameworkVersion", new Muxer().SharedFxVersion },
            { "NetStandardImplicitPackageVersion", new FrameworkDependencyFile().GetNetStandardLibraryVersion() ?? "" },
        };
        return new CliTemplateEngineHost(
            HostIdentifier,
            Product.Version,
            preferences,
            builtIns,
            outputPath: outputPath?.FullName,
            logLevel: logLevel);
    }
}
